﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MandelbrotLib;

namespace Mandelbrot
{
    public partial class MandelbrotForm : Form
    {
        private enum CursorState
        {
            Idle,
            Tracking,
        }

        private CursorState mCursorState = CursorState.Idle;
        private Point mMDPos;
        private Point mOldPos;

        private Rectangle mOldRect;
        private Rectangle mNewRect;

        private RealNumber mX0_pre;
        private RealNumber mY0_pre;
        private RealNumber mX1_pre;
        private RealNumber mY1_pre;

        private RealNumber mX0;
        private RealNumber mY0;
        private RealNumber mX1;
        private RealNumber mY1;
        private int mNumIterations = 50;
        private MandelbrotSet mMandelbrot = new MandelbrotSet();

        private Bitmap[] mImageBuffer = new Bitmap[2];
        private int mImageBufferIndex = 0;

        private bool mHandleResize = false;

        public MandelbrotForm()
        {
            InitializeComponent();
        }

        private void pbxMandelbrot_MouseDown(object sender, MouseEventArgs e)
        {
            mMDPos = new Point(e.X, e.Y);
            mOldPos = mMDPos;
            mCursorState = CursorState.Tracking;
        }

        private void pbxMandelbrot_MouseUp(object sender, MouseEventArgs e)
        {
            EasySettingsForm dlg = new EasySettingsForm();
            dlg.NumOfIterations = this.mNumIterations;
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                this.mNumIterations = dlg.NumOfIterations;
            }

            mCursorState = CursorState.Idle;
            pbxMandelbrot.Invalidate();

            int w = pbxMandelbrot.Width;
            int h = pbxMandelbrot.Height;

            RealNumber rx = mX1 - mX0;
            RealNumber ry = mY1 - mY0;

            mX0_pre = mX0 + rx * mNewRect.X / w;
            mX1_pre = mX0 + rx * mNewRect.Right / w;
            mY0_pre = mY0 + ry * (h - mNewRect.Bottom) / h;
            mY1_pre = mY0 + ry * (h - mNewRect.Y) / h;

            CalculateRegion();
            Redraw();
        }

        private void pbxMandelbrot_MouseMove(object sender, MouseEventArgs e)
        {
            if (mCursorState == CursorState.Tracking)
            {
                int x00 = Math.Min(mMDPos.X, mOldPos.X);
                int y00 = Math.Min(mMDPos.Y, mOldPos.Y);
                int x01 = Math.Max(mMDPos.X, mOldPos.X);
                int y01 = Math.Max(mMDPos.Y, mOldPos.Y);
                mOldRect = new Rectangle(x00, y00, x01 - x00, y01 - y00);


                int x10 = Math.Min(mMDPos.X, e.X);
                int y10 = Math.Min(mMDPos.Y, e.Y);
                int x11 = Math.Max(mMDPos.X, e.X);
                int y11 = Math.Max(mMDPos.Y, e.Y);
                Pen pen = new Pen(new SolidBrush(Color.Black));
                mNewRect = new Rectangle(x10, y10, x11 - x10, y11 - y10);


                Rectangle invlRect = Rectangle.Union(mOldRect, mNewRect);
                invlRect.Width++;
                invlRect.Height++;

                mOldPos = new Point(e.X, e.Y);

                // repaint
                pbxMandelbrot.Invalidate(invlRect);
            }
        }

        private void pbxMandelbrot_Resize(object sender, EventArgs e)
        {
            if (mHandleResize)
            {
                HandleResize();
            }
        }

        private void HandleResize()
        {
            CreateImageBuffer();
            CalculateRegion();
            Redraw();
        }

        private void SwapBuffer()
        {
            mImageBufferIndex = 1 - mImageBufferIndex;
            pbxMandelbrot.Invalidate();
        }

        private void CalculateRegion()
        {
            int w = pbxMandelbrot.Width;
            int h = pbxMandelbrot.Height;

            RealNumber rx = mX1_pre - mX0_pre;
            RealNumber ry = mY1_pre - mY0_pre;

            RealNumber gx = rx / w;
            RealNumber gy = ry / h;

            if (gx < gy)
            {   // expand x according to y
                RealNumber newRx = (ry * w) / h;
                RealNumber mod = (newRx - rx) / 2;

                mX0_pre = mX0_pre - mod;
                mX1_pre = mX1_pre + mod;
            }
            else if (gx > gy)
            {   // expand y according to x
                RealNumber newRy = (rx * h) / w;
                RealNumber mod = (newRy - ry) / 2;

                mY0_pre = mY0_pre - mod;
                mY1_pre = mY1_pre + mod;
            }
        }

        private void Redraw()
        {
            int w = pbxMandelbrot.Width;
            int h = pbxMandelbrot.Height;

            mX0 = mX0_pre;
            mY0 = mY0_pre;
            mX1 = mX1_pre;
            mY1 = mY1_pre;

            mX0.RoundTo(1);
            mY0.RoundTo(1);
            mX1.RoundTo(1);
            mY1.RoundTo(1);

            mMandelbrot.SetRegion(mX0, mY0, mX1, mY1, w, h);
            mMandelbrot.SetNumberOfIterations(mNumIterations);
            mMandelbrot.Calculate();

            Bitmap image = mImageBuffer[1 - mImageBufferIndex];

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    int v = mMandelbrot[i, j];
                    v = v * 255 / mNumIterations;
                    Color color = Color.FromArgb(v, v, v);
                    image.SetPixel(i, h - j - 1, color);
                }
            }

            this.MainStatusStrip.Items[0].Text = "Iterations: "
                + mNumIterations.ToString();
            this.MainStatusStrip.Items[1].Text = "Precesion: " 
                + mMandelbrot.Precision.ToString();

            SwapBuffer();
        }

        private void pbxMandelbrot_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.DrawImage(mImageBuffer[mImageBufferIndex], 
                new Point(0, 0));

            if (mCursorState == CursorState.Tracking)
            {
                using (Pen eraser = new Pen(new SolidBrush(Color.Transparent)))
                using (Pen pen = new Pen(new SolidBrush(Color.Red)))
                {
                    e.Graphics.DrawRectangle(eraser, mOldRect);
                    e.Graphics.DrawRectangle(pen, mNewRect);
                }
            }
        }

        private void CreateImageBuffer()
        {
            if (pbxMandelbrot.Width == 0 || pbxMandelbrot.Height == 0)
            {
                return;
            }
            for (int i = 0; i < mImageBuffer.Length; i++)
            {
                mImageBuffer[i] = new Bitmap(pbxMandelbrot.Width, pbxMandelbrot.Height);
            }
            mImageBufferIndex = 0;
        }

        private void MandelbrotForm_Load(object sender, EventArgs e)
        {
            mHandleResize = false;

            InitializeParameter();

            InitializeLayout();

            CreateImageBuffer();

            UpdateLayout();

            mHandleResize = true;

            HandleResize();
        }

        private void InitializeParameter()
        {
            mNumIterations = 100;

            mX0_pre = RealNumber.CreateByNumerator(-5).Mul(RealNumber.CreateByDenominator(2));
            mX1_pre = RealNumber.CreateByNumerator(3).Mul(RealNumber.CreateByDenominator(2));

            mY0_pre = RealNumber.CreateByNumerator(-2);
            mY1_pre = RealNumber.CreateByNumerator(2);
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void pbxMandelbrot_Click(object sender, EventArgs e)
        {

        }

        private void optionToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void InitializeLayout()
        {
            this.MainStatusStrip.Items.Add("Iterations: ...");
            this.MainStatusStrip.Items.Add("Precision: ...");
        }

        private void UpdateLayout()
        {
            pbxMandelbrot.Height = this.ClientSize.Height 
                - this.MainStatusStrip.Height - this.MainMenu.Height;
        }

        private void MandelbrotForm_Resize(object sender, EventArgs e)
        {
            UpdateLayout();
        }
    }
}
